Дізнайтеся про гексагональну та чисту архітектури для створення фронтенд-додатків, що легко підтримувати, масштабувати та тестувати. Вивчіть їхні принципи та переваги.
Архітектура фронтенду: Гексагональна та чиста архітектури для масштабованих додатків
Зі зростанням складності фронтенд-додатків, добре продумана архітектура стає вирішальною для підтримки, тестування та масштабування. Два популярні архітектурні патерни, що вирішують ці проблеми, — це гексагональна архітектура (також відома як порти та адаптери) і чиста архітектура. Хоча ці принципи походять зі світу бекенду, їх можна ефективно застосовувати у фронтенд-розробці для створення надійних та адаптивних користувацьких інтерфейсів.
Що таке архітектура фронтенду?
Архітектура фронтенду визначає структуру, організацію та взаємодію різних компонентів у фронтенд-додатку. Вона слугує планом для створення, підтримки та масштабування додатка. Хороша архітектура фронтенду сприяє:
- Підтримуваність: Легкість розуміння, модифікації та налагодження коду.
- Тестованість: Спрощення написання юніт- та інтеграційних тестів.
- Масштабованість: Дозволяє додатку справлятися зі зростаючою складністю та навантаженням користувачів.
- Повторне використання: Сприяє повторному використанню коду в різних частинах додатка.
- Гнучкість: Адаптується до мінливих вимог та нових технологій.
Без чіткої архітектури фронтенд-проєкти можуть швидко стати монолітними та складними в управлінні, що призводить до збільшення витрат на розробку та зниження гнучкості.
Вступ до гексагональної архітектури
Гексагональна архітектура, запропонована Алістером Коберном, має на меті відокремити основну бізнес-логіку додатка від зовнішніх залежностей, таких як бази даних, UI-фреймворки та сторонні API. Це досягається за допомогою концепції портів та адаптерів.
Ключові концепції гексагональної архітектури:
- Ядро (Домен): Містить бізнес-логіку та сценарії використання додатка. Воно незалежне від будь-яких зовнішніх фреймворків або технологій.
- Порти: Інтерфейси, що визначають, як ядро взаємодіє із зовнішнім світом. Вони представляють вхідні та вихідні межі ядра.
- Адаптери: Реалізації портів, які з'єднують ядро з конкретними зовнішніми системами. Існує два типи адаптерів:
- Рушійні адаптери (первинні адаптери): Ініціюють взаємодію з ядром. Приклади: UI-компоненти, інтерфейси командного рядка або інші додатки.
- Керовані адаптери (вторинні адаптери): Викликаються ядром для взаємодії із зовнішніми системами. Приклади: бази даних, API або файлові системи.
Ядро нічого не знає про конкретні адаптери. Воно взаємодіє з ними лише через порти. Таке відокремлення дозволяє легко замінювати різні адаптери, не впливаючи на логіку ядра. Наприклад, ви можете перейти з одного UI-фреймворка (наприклад, React) на інший (наприклад, Vue.js), просто замінивши рушійний адаптер.
Переваги гексагональної архітектури:
- Покращена тестованість: Основну бізнес-логіку можна легко тестувати ізольовано, не покладаючись на зовнішні залежності. Ви можете використовувати мок-адаптери для симуляції поведінки зовнішніх систем.
- Підвищена підтримуваність: Зміни в зовнішніх системах мають мінімальний вплив на логіку ядра. Це полегшує підтримку та розвиток додатка з часом.
- Більша гнучкість: Ви можете легко адаптувати додаток до нових технологій та вимог, додаючи або замінюючи адаптери.
- Покращене повторне використання: Основну бізнес-логіку можна повторно використовувати в різних контекстах, підключаючи її до різних адаптерів.
Вступ до чистої архітектури
Чиста архітектура, популяризована Робертом Мартіном (Дядько Боб), — це ще один архітектурний патерн, який наголошує на розділенні відповідальностей та роз'єднанні. Він зосереджений на створенні системи, незалежної від фреймворків, баз даних, UI та будь-яких зовнішніх агентів.
Ключові концепції чистої архітектури:
Чиста архітектура організовує додаток у концентричні шари, де найбільш абстрактний та повторно використовуваний код знаходиться в центрі, а найбільш конкретний та технологічно-специфічний — на зовнішніх шарах.
- Сутності (Entities): Представляють основні бізнес-об'єкти та правила додатка. Вони незалежні від будь-яких зовнішніх систем.
- Сценарії використання (Use Cases): Визначають бізнес-логіку додатка та те, як користувачі взаємодіють із системою. Вони керують сутностями для виконання конкретних завдань.
- Адаптери інтерфейсу (Interface Adapters): Конвертують дані між сценаріями використання та зовнішніми системами. Цей шар включає презентери, контролери та шлюзи.
- Фреймворки та драйвери (Frameworks and Drivers): Зовнішній шар, що містить UI-фреймворк, базу даних та інші зовнішні технології.
Правило залежностей у чистій архітектурі говорить, що зовнішні шари можуть залежати від внутрішніх, але внутрішні шари не можуть залежати від зовнішніх. Це гарантує, що основна бізнес-логіка є незалежною від будь-яких зовнішніх фреймворків або технологій.
Переваги чистої архітектури:
- Незалежність від фреймворків: Архітектура не покладається на існування якоїсь бібліотеки з багатофункціональним програмним забезпеченням. Це дозволяє вам використовувати фреймворки як інструменти, а не бути змушеними вписувати свою систему в їхні обмежені рамки.
- Тестованість: Бізнес-правила можна тестувати без UI, бази даних, вебсервера чи будь-якого іншого зовнішнього елемента.
- Незалежність від UI: UI можна легко змінити, не змінюючи решту системи. Веб-інтерфейс можна замінити на консольний, не змінюючи жодного з бізнес-правил.
- Незалежність від бази даних: Ви можете замінити Oracle або SQL Server на Mongo, BigTable, CouchDB або щось інше. Ваші бізнес-правила не прив'язані до бази даних.
- Незалежність від будь-якого зовнішнього агента: Насправді ваші бізнес-правила просто *нічого* не знають про зовнішній світ.
Застосування гексагональної та чистої архітектури у фронтенд-розробці
Хоча гексагональна та чиста архітектури часто асоціюються з бекенд-розробкою, їхні принципи можна ефективно застосовувати до фронтенд-додатків для покращення їхньої архітектури та підтримуваності. Ось як це зробити:
1. Визначте ядро (домен)
Перший крок — визначити основну бізнес-логіку вашого фронтенд-додатка. Це включає сутності, сценарії використання та бізнес-правила, які не залежать від UI-фреймворка чи будь-яких зовнішніх API. Наприклад, у додатку для електронної комерції ядро може включати логіку управління продуктами, кошиком для покупок та замовленнями.
Приклад: У додатку для управління завданнями основний домен може складатися з:
- Сутності: Завдання, Проєкт, Користувач
- Сценарії використання: СтворитиЗавдання, ОновитиЗавдання, ПризначитиЗавдання, ЗавершитиЗавдання, ПерелічитиЗавдання
- Бізнес-правила: Завдання повинно мати назву, завдання не можна призначити користувачу, який не є членом проєкту.
2. Визначте порти та адаптери (гексагональна архітектура) або шари (чиста архітектура)
Далі, визначте порти та адаптери (гексагональна архітектура) або шари (чиста архітектура), які відокремлюють ядро від зовнішніх систем. У фронтенд-додатку це можуть бути:
- UI-компоненти (рушійні адаптери/фреймворки та драйвери): Компоненти React, Vue.js, Angular, що взаємодіють з користувачем.
- API-клієнти (керовані адаптери/адаптери інтерфейсу): Сервіси, що роблять запити до бекенд-API.
- Сховища даних (керовані адаптери/адаптери інтерфейсу): Local storage, IndexedDB або інші механізми зберігання даних.
- Управління станом (адаптери інтерфейсу): Redux, Vuex або інші бібліотеки для управління станом.
Приклад з використанням гексагональної архітектури:
- Ядро: Логіка управління завданнями (сутності, сценарії використання, бізнес-правила).
- Порти:
TaskService(визначає методи для створення, оновлення та отримання завдань). - Рушійний адаптер: Компоненти React, що використовують
TaskServiceдля взаємодії з ядром. - Керований адаптер: API-клієнт, що реалізує
TaskServiceта робить запити до бекенд-API.
Приклад з використанням чистої архітектури:
- Сутності: Task, Project, User (чисті JavaScript-об'єкти).
- Сценарії використання: CreateTaskUseCase, UpdateTaskUseCase (керують сутностями).
- Адаптери інтерфейсу:
- Контролери: Обробляють введення користувача з UI.
- Презентери: Форматують дані для відображення в UI.
- Шлюзи: Взаємодіють з API-клієнтом.
- Фреймворки та драйвери: Компоненти React, API-клієнт (axios, fetch).
3. Реалізуйте адаптери (гексагональна архітектура) або шари (чиста архітектура)
Тепер реалізуйте адаптери або шари, що з'єднують ядро із зовнішніми системами. Переконайтеся, що адаптери або шари незалежні від ядра, і що ядро взаємодіє з ними лише через порти або інтерфейси. Це дозволяє легко замінювати різні адаптери або шари, не впливаючи на логіку ядра.
Приклад (гексагональна архітектура):
// Порт TaskService
interface TaskService {
createTask(taskData: TaskData): Promise;
updateTask(taskId: string, taskData: TaskData): Promise;
getTask(taskId: string): Promise;
}
// Адаптер API-клієнта
class ApiTaskService implements TaskService {
async createTask(taskData: TaskData): Promise {
// Робимо API-запит для створення завдання
}
async updateTask(taskId: string, taskData: TaskData): Promise {
// Робимо API-запит для оновлення завдання
}
async getTask(taskId: string): Promise {
// Робимо API-запит для отримання завдання
}
}
// Адаптер компонента React
function TaskList() {
const taskService: TaskService = new ApiTaskService();
const handleCreateTask = async (taskData: TaskData) => {
await taskService.createTask(taskData);
// Оновити список завдань
};
// ...
}
Приклад (чиста архітектура):
// Сутності
class Task {
constructor(public id: string, public title: string, public description: string) {}
}
// Сценарій використання
class CreateTaskUseCase {
constructor(private taskGateway: TaskGateway) {}
async execute(title: string, description: string): Promise {
const task = new Task(generateId(), title, description);
await this.taskGateway.create(task);
return task;
}
}
// Адаптери інтерфейсу - Шлюз
interface TaskGateway {
create(task: Task): Promise;
}
class ApiTaskGateway implements TaskGateway {
async create(task: Task): Promise {
// Робимо API-запит для створення завдання
}
}
// Адаптери інтерфейсу - Контролер
class TaskController {
constructor(private createTaskUseCase: CreateTaskUseCase) {}
async createTask(req: Request, res: Response) {
const { title, description } = req.body;
const task = await this.createTaskUseCase.execute(title, description);
res.json(task);
}
}
// Фреймворки та драйвери - Компонент React
function TaskForm() {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const apiTaskGateway = new ApiTaskGateway();
const createTaskUseCase = new CreateTaskUseCase(apiTaskGateway);
const taskController = new TaskController(createTaskUseCase);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await taskController.createTask({ body: { title, description } } as Request, { json: (data: any) => console.log(data) } as Response);
};
return (
);
}
4. Реалізуйте впровадження залежностей
Щоб ще більше роз'єднати ядро від зовнішніх систем, використовуйте впровадження залежностей для надання адаптерів або шарів ядру. Це дозволяє легко замінювати різні реалізації адаптерів або шарів, не змінюючи код ядра.
Приклад:
// Впроваджуємо TaskService у компонент TaskList
function TaskList(props: { taskService: TaskService }) {
const { taskService } = props;
const handleCreateTask = async (taskData: TaskData) => {
await taskService.createTask(taskData);
// Оновити список завдань
};
// ...
}
// Використання
const apiTaskService = new ApiTaskService();
5. Напишіть юніт-тести
Однією з ключових переваг гексагональної та чистої архітектури є покращена тестованість. Ви можете легко писати юніт-тести для основної бізнес-логіки, не покладаючись на зовнішні залежності. Використовуйте мок-адаптери або шари для симуляції поведінки зовнішніх систем та перевірки, що логіка ядра працює, як очікувалося.
Приклад:
// Мок-сервіс TaskService
class MockTaskService implements TaskService {
async createTask(taskData: TaskData): Promise {
return Promise.resolve({ id: '1', ...taskData });
}
async updateTask(taskId: string, taskData: TaskData): Promise {
return Promise.resolve({ id: taskId, ...taskData });
}
async getTask(taskId: string): Promise {
return Promise.resolve({ id: taskId, title: 'Test Task', description: 'Test Description' });
}
}
// Юніт-тест
describe('TaskList', () => {
it('повинен створювати завдання', async () => {
const mockTaskService = new MockTaskService();
const taskList = new TaskList({ taskService: mockTaskService });
const taskData = { title: 'Нове завдання', description: 'Новий опис' };
const newTask = await taskList.handleCreateTask(taskData);
expect(newTask.title).toBe('Нове завдання');
expect(newTask.description).toBe('Новий опис');
});
});
Практичні міркування та виклики
Хоча гексагональна та чиста архітектури пропонують значні переваги, існують також деякі практичні міркування та виклики, які слід враховувати при їх застосуванні у фронтенд-розробці:
- Збільшена складність: Ці архітектури можуть додати складності до кодової бази, особливо для малих або простих додатків.
- Крива навчання: Розробникам може знадобитися вивчити нові концепції та патерни для ефективної реалізації цих архітектур.
- Надмірне ускладнення (Over-engineering): Важливо уникати надмірного ускладнення додатка. Починайте з простої архітектури та поступово додавайте складність за потреби.
- Баланс абстракції: Знайти правильний рівень абстракції може бути складно. Занадто багато абстракції може ускладнити розуміння коду, тоді як занадто мало — призвести до сильної зв'язаності.
- Міркування щодо продуктивності: Надмірна кількість шарів абстракції може потенційно вплинути на продуктивність. Важливо профілювати додаток та виявляти будь-які вузькі місця у продуктивності.
Міжнародні приклади та адаптації
Принципи гексагональної та чистої архітектури застосовні до фронтенд-розробки незалежно від географічного розташування чи культурного контексту. Однак конкретні реалізації та адаптації можуть відрізнятися залежно від вимог проєкту та вподобань команди розробників.
Приклад 1: Глобальна платформа електронної комерції
Глобальна платформа електронної комерції може використовувати гексагональну архітектуру для відокремлення основної логіки кошика для покупок та управління замовленнями від UI-фреймворка та платіжних шлюзів. Ядро відповідатиме за управління продуктами, розрахунок цін та обробку замовлень. Рушійні адаптери включатимуть компоненти React для каталогу продуктів, кошика та сторінок оформлення замовлення. Керовані адаптери включатимуть API-клієнти для різних платіжних шлюзів (наприклад, Stripe, PayPal, Alipay) та постачальників послуг доставки (наприклад, FedEx, DHL, UPS). Це дозволяє платформі легко адаптуватися до різних регіональних методів оплати та варіантів доставки.
Приклад 2: Багатомовний додаток соціальної мережі
Багатомовний додаток соціальної мережі може використовувати чисту архітектуру для відокремлення основної логіки аутентифікації користувачів та управління контентом від UI та фреймворків локалізації. Сутності представлятимуть користувачів, пости та коментарі. Сценарії використання визначатимуть, як користувачі створюють, діляться та взаємодіють з контентом. Адаптери інтерфейсу оброблятимуть переклад контенту на різні мови та форматування даних для різних UI-компонентів. Це дозволяє додатку легко підтримувати нові мови та адаптуватися до різних культурних уподобань.
Висновок
Гексагональна та чиста архітектури надають цінні принципи для створення фронтенд-додатків, які легко підтримувати, тестувати та масштабувати. Відокремлюючи основну бізнес-логіку від зовнішніх залежностей, ви можете створити більш гнучку та адаптивну кодову базу, яку легше розвивати з часом. Хоча ці архітектури можуть додати початкової складності, довгострокові переваги з точки зору підтримуваності, тестованості та масштабованості роблять їх вартою інвестицією для складних фронтенд-проєктів. Пам'ятайте, що слід починати з простої архітектури та поступово додавати складність за потреби, а також ретельно враховувати практичні міркування та виклики.
Застосовуючи ці архітектурні патерни, фронтенд-розробники можуть створювати більш надійні та стабільні додатки, що здатні задовольнити мінливі потреби користувачів по всьому світу.
Додаткові матеріали для читання
- Hexagonal Architecture: https://alistaircockburn.com/hexagonal-architecture/
- Clean Architecture: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html